import copy
import json
import time
import numpy as np
import struct


def loadImageSet(filename):
    binfile = open(filename, 'rb')  # 读取二进制文件
    buffers = binfile.read()

    head = struct.unpack_from('>IIII', buffers, 0)  # 取前4个整数，返回一个元组

    offset = struct.calcsize('>IIII')  # 定位到data开始的位置
    imgNum = head[1]
    width = head[2]
    height = head[3]

    bits = imgNum * width * height  # data一共有60000*28*28个像素值
    bitsString = '>' + str(bits) + 'B'  # fmt格式：'>47040000B'

    imgs = struct.unpack_from(bitsString, buffers, offset)  # 取data数据，返回一个元组

    binfile.close()
    imgs = np.reshape(imgs, [imgNum, width * height])  # reshape为[60000,784]型数组

    return imgs, head


def loadLabelSet(filename):
    binfile = open(filename, 'rb')  # 读二进制文件
    buffers = binfile.read()

    head = struct.unpack_from('>II', buffers, 0)  # 取label文件前2个整形数

    labelNum = head[1]
    offset = struct.calcsize('>II')  # 定位到label数据开始的位置

    numString = '>' + str(labelNum) + "B"  # fmt格式：'>60000B'
    labels = struct.unpack_from(numString, buffers, offset)  # 取label数据

    binfile.close()
    labels = np.reshape(labels, [labelNum])  # 转型为列表(一维数组)

    return labels, head


def load_37_data(train_count=100):
    """
    加载37训练数据和标签
    :param train_count: 希望训练的数据多少张
    :return:
    """
    file1 = 'train-images-idx3-ubyte'
    file2 = 'train-labels-idx1-ubyte'

    imgs, data_head = loadImageSet(file1)
    labels, labels_head = loadLabelSet(file2)

    array_data = list()
    array_labels = list()

    count = 0
    for image_index in range(0, 60000):
        if count >= train_count:
            break
        if labels[image_index] in [3, 7]:
            # a = np.reshape(imgs[image_index, :], [28, 28]).tolist()
            # for i in range(0, len(a) - 1):
            #     print(a[i])
            # print(labels[image_index])

            count += 1
            array_data.append(np.reshape(imgs[image_index, :], [28 * 28]).tolist())
            if labels[image_index] == 3:
                array_labels.append(0)
            if labels[image_index] == 7:
                array_labels.append(1)
    return array_data, array_labels


class Perceptron(object):
    def __init__(self, size):

        self.size = size
        # randn 是正态分布，有正有负 返回1维， 1个数据
        # self.bias = np.random.randn(size[1], 1)
        self.bias = np.random.randn(1)
        # self.bias = np.zeros(1)
        # 返回1维， 28*28个数据
        # self.weights = np.random.randn(size[1], size[0])
        self.weights = np.random.randn(size[0])
        self.weights = np.zeros(784)
        self.r = 0.02
        self.sample = dict()

    def save_model(self, count):
        with open('t.json', 'w') as f:
            json.dump(json.dumps(self.sample), f)
        # data = json.dumps(self.sample)

        print("----------我是模型保存的分割线-----------")
        print("weight:")
        print(self.weights)
        print("bias:")
        print(self.bias)

        print("count:" + str(count))

    def append_sample(self, input, weight, bias, predict_label, count, train_time):
        """
        保存每次训练的值，用作第二集交互3
        :param count: 第几张图片
        :return:
        """
        sample_list = self.sample.get(str(count), dict()).get('train', list())
        weight = map(lambda x: round(x, 2), weight.tolist())
        weight = list(weight)
        sample_list.append({
            'train_time': train_time,
            'train_out_num': 3 if predict_label == 0 else 7,
            'weight': weight,
            'bias': round(bias[0], 2)
        })
        self.sample[str(count)]['train'] = sample_list

    def train(self, input_x, real_label, count):
        sum37 = np.dot(np.array(input_x), self.weights) + self.bias
        predict_label = step(sum37)

        # 如果目标值与输出值不同，则进行训练
        train_time = 0
        start_time = time.time()
        self.sample[str(count)] = {
            'input_num': 3 if real_label == 0 else 7,
            'input': input_x,
            'weight': list(map(lambda x: round(x, 2), self.weights.tolist())),
            'bias': round(self.bias[0], 2),
            'output_num': 3 if predict_label == 0 else 7,
        }
        while real_label != predict_label:
            # self.sample[str(count)] = {
            #     'input_num': 3 if real_label == 0 else 7,
            #     'input': input_x,
            #     'weight': list(map(lambda x: round(x, 2), self.weights.tolist())),
            #     'bias': round(self.bias[0], 2)
            # }

            train_time += 1
            before_weights = np.copy(self.weights),
            before_bias = np.copy(self.bias)

            input_x = np.array(input_x)
            delta_w = input_x * (real_label - predict_label)
            delta_w = delta_w * self.r
            self.weights += delta_w
            delta_b = (real_label - predict_label) * self.r
            self.bias += np.array([delta_b])
            predict_label = step(np.dot(np.array(input_x), self.weights) + self.bias)
            # self.append_sample(input_x, before_weights, self.weights, before_bias, self.bias, predict_label, count, train_time)
            self.append_sample(input_x, self.weights, self.bias, predict_label, count, train_time)

            end_time = time.time()
            # 每十秒打印一次
            if end_time - start_time > 1:
                start_time = time.time()
                print("weight:")
                print(self.weights)
                print("bias:")
                print(self.bias)
                print("real_label-predict_label:")
                print(real_label - predict_label)
                print("count:" + str(count))

    def test(self, x, y):
        test_count = 0
        correct_count = 0
        for b, w in zip(self.bias, self.weights):
            test_count += 1
            z = sigmoid(np.dot(w, x) + b)
            if z == y:
                correct_count += 1
            if test_count > 50:
                break
        if correct_count / test_count > pass_ratio:
            return 1
        else:
            return 0


# sigmoid 函数
def sigmoid(z):
    return 1.0 / (1.0 + np.exp(-z))


# 阶跃函数
def step(z):
    if z > 0 or z == 0:
        return 1
    else:
        return 0


pass_ratio = 0.7

test_round = 0

# pct = Perceptron((6,1))
#
# x=((3,1,1,3,1,1),(3,4,4,3,4,5),(3,2,3,2,3,2))
# y=(1,2,1)
#
# for i in range(0,2):
# 	pct.train(x[i],y[i])


pct = Perceptron((28 * 28, 1))

array_data, labels = load_37_data(200)
print(labels)

for i in range(0, len(labels) - 1):
    pct.train(array_data[i], labels[i], i)
    if len(labels) - i < 4:
        break

pct.save_model(1)
print("******")
